package xaj.meta.visit;

import java.util.ArrayList;
import java.util.List;

import polyglot.ast.Assign;
import polyglot.ast.Block;
import polyglot.ast.Call;
import polyglot.ast.Expr;
import polyglot.ast.NodeFactory;
import polyglot.ast.TypeNode;
import polyglot.ast.Assign.Operator;
import polyglot.ext.jl.parse.Name;
import polyglot.frontend.Job;
import polyglot.types.TypeSystem;
import polyglot.util.Position;
import xaj.meta.ExtensionInfo;
import xaj.scc.ast.Factor;
import xaj.scc.ast.LambdaFactor;
import xaj.scc.ast.NamedFactor;
import xaj.scc.ast.Production;
import xaj.scc.ast.ProductionElement;
import xaj.scc.ast.SCNodeFactory;
import xaj.scc.ast.SemanticAction;

public class Visitor_Dudu_02 extends XAJVisitor {

	private Visitor_Dudu_01 vd1;

	private List<NamedFactor> namedFactor;

	private Production production;

	public Visitor_Dudu_02(Job job, TypeSystem typeSystem,
			NodeFactory nodeFactory, Visitor_Dudu_01 vd1) {
		super(job, typeSystem, nodeFactory);
		namedFactor = new ArrayList<NamedFactor>();
		this.vd1 = vd1;
	}

	public void visitNamedFactor(NamedFactor namf) {
		if (getVisitType() == VisitType.LEAVE) {
			Factor factor = namf.getFactor();
			if (factor instanceof NamedFactor) {
				NamedFactor n = namf;
				n = n.setFactor(((NamedFactor) factor).getFactor());
				namedFactor.remove(factor);
				changeNode(n);
			}
			namedFactor.add(namf);
		}
	}

	/* Fixa producoes sem acao semantica */
	public void visitSemanticAction(SemanticAction sa) {
		if (getVisitType() == VisitType.LEAVE) {
			if (sa.getBlock() == null) {

				List ls = new ArrayList<Expr>();

				Position pos = sa.position();
				Name name = new Name(ExtensionInfo.parser, pos, "RESULT");
				Expr target = name.toExpr();
				List listaVazia = new ArrayList();
				Name par1 = new Name(ExtensionInfo.parser, pos,
						"parser.position()");
				listaVazia.add(par1.toExpr());
				TypeNode typeNameOfSemanticAction = nf.AmbTypeNode(pos,
						"multiIntro" + "." + production.getLeftSide());
				Expr source = nf.New(pos, typeNameOfSemanticAction, listaVazia);
				Operator op = Assign.ASSIGN;
				Assign assign = nf.Assign(pos, target, op, source);

				ls.add(nf.Eval(pos, assign));

				List<ProductionElement> l = production.getRightSide();
				for (NamedFactor p : namedFactor) {

					Name n = new Name(ExtensionInfo.parser, sa.position(),
							"RESULT");
					List ls1 = new ArrayList();
					Name n1 = new Name(ExtensionInfo.parser, sa.position(),
							'\"' + p.getName() + '\"');
					Name n2 = new Name(ExtensionInfo.parser, sa.position(), p
							.getName());
					ls1.add(n1.toExpr());
					ls1.add(n2.toExpr());
					Call call = nf.Call(pos, n.toReceiver(), "add", ls1);
					ls.add(nf.Eval(pos, call));

				}

				Block b = nf.Block(pos, ls);

				changeNode(sa.setBlock(b));

			}

		}
	}

	public void visitProduction(Production pd) {
		production = pd;

	}

	private String getNewMap() {
		return "MapImpl";
	}

	private String getNewList() {
		return "ListImpl";
	}

	public void visitProductionElement(ProductionElement pe) {
		if (getVisitType() == VisitType.LEAVE) {
			SCNodeFactory nf = (SCNodeFactory) nodeFactory();
			Position pos = pe.position();
			
			//SemanticAction das novas producoes do []
			for (ProductionElement p : vd1
					.getProductionsElementsOfOptionalFactor()) {

				if (pe.getFactor() == p.getFactor()) {
					if (p.getFactor() instanceof LambdaFactor) {

						Name name = new Name(ExtensionInfo.parser, pos,
								"RESULT");
						Expr target = name.toExpr();
						List listaVazia = new ArrayList();
						TypeNode typeMapImpl = nf.AmbTypeNode(pos, getNewMap());
						Expr source = nf.New(pos, typeMapImpl, listaVazia);
						Operator op = Assign.ASSIGN;
						Assign assign = nf.Assign(pos, target, op, source);
						Block block = nf.Block(pos, nf.Eval(pos, assign));
						SemanticAction sa = nf.SemanticAction(pos, block);
						ProductionElement production = nf.ProductionElement(pe
								.position(), p.getFactor(), sa);
						changeNode(production);
					} else {

						changeNode(createAddCalls(pos, pe.getFactor(),
								new ArrayList<Expr>()));

					}
				}
			}

			//SemanticAction das novas producoes do ()
			for (ProductionElement p : vd1.getProductionsElementsOfOrFactor()) {
				if (p.getFactor() == pe.getFactor()) {

					changeNode(createAddCalls(pos, pe.getFactor(),
							new ArrayList<Expr>()));
				}

			}
			
			//SemanticAction das novas producoes do {}
			for (ProductionElement p : vd1
					.getProductionsElementsOfClosureFactor()) {
				if (p.getFactor() == pe.getFactor()) {
					if (p.getFactor() instanceof LambdaFactor) {
						Name name = new Name(ExtensionInfo.parser, pos,
								"RESULT");
						Expr target = name.toExpr();
						List listaVazia = new ArrayList();
						TypeNode typeListImpl = nf.AmbTypeNode(pos,
								getNewList());
						Expr source = nf.New(pos, typeListImpl, listaVazia);
						Operator op = Assign.ASSIGN;
						Assign assign = nf.Assign(pos, target, op, source);
						List ls = new ArrayList<Expr>();
						ls.add(nf.Eval(pos, assign));
						Block b = nf.Block(pos, ls);
						ProductionElement production = nf.ProductionElement(
								pos, pe.getFactor(), nf.SemanticAction(pos, b));
						changeNode(production);
					} else {
						Name name = new Name(ExtensionInfo.parser, pos,
								"RESULT");
						Expr target = name.toExpr();
						Name name2 = new Name(ExtensionInfo.parser, pos,
								namedFactor.get(0).getName());
						Expr source = name2.toExpr();
						namedFactor.remove(0);
						Operator op = Assign.ASSIGN;
						Assign assign = nf.Assign(pos, target, op, source);
						ArrayList ls = new ArrayList<Expr>();
						ls.add(nf.Eval(pos, assign));
						for (NamedFactor named : namedFactor) {

							Name n = new Name(ExtensionInfo.parser, pos,
									"RESULT");
							List ls1 = new ArrayList();
							Name n1 = new Name(ExtensionInfo.parser, pos,
									'\"' + named.getName() + '\"');
							Name n2 = new Name(ExtensionInfo.parser, pos, named
									.getName());
							ls1.add(n1.toExpr());
							ls1.add(n2.toExpr());
							Call call = nf
									.Call(pos, n.toReceiver(), "add", ls1);
							ls.add(nf.Eval(pos, call));
							Block b = nf.Block(pos, ls);
							ProductionElement production = nf
									.ProductionElement(pos, pe.getFactor(), nf
											.SemanticAction(pos, b));
							changeNode(production);

						}
					}
				}
			}

			namedFactor.clear();
		}
	}

	// Codigo repetido transformado em uma funcao
	public ProductionElement createAddCalls(Position pos, Factor factor,
			ArrayList ls) {
		SCNodeFactory nf = (SCNodeFactory) nodeFactory();
		Name name = new Name(ExtensionInfo.parser, pos, "RESULT");
		Expr target = name.toExpr();
		List listaVazia = new ArrayList();
		TypeNode typeMapImpl = nf.AmbTypeNode(pos, getNewMap());
		Expr source = nf.New(pos, typeMapImpl, listaVazia);
		Operator op = Assign.ASSIGN;
		Assign assign = nf.Assign(pos, target, op, source);

		ls.add(nf.Eval(pos, assign));

		List<ProductionElement> l = production.getRightSide();
		for (NamedFactor named : namedFactor) {

			Name n = new Name(ExtensionInfo.parser, pos, "RESULT");
			List ls1 = new ArrayList();
			Name n1 = new Name(ExtensionInfo.parser, pos, '\"' + named
					.getName() + '\"');
			Name n2 = new Name(ExtensionInfo.parser, pos, named.getName());
			ls1.add(n1.toExpr());
			ls1.add(n2.toExpr());
			Call call = nf.Call(pos, n.toReceiver(), "add", ls1);
			ls.add(nf.Eval(pos, call));

		}

		Block b = nf.Block(pos, ls);
		ProductionElement production = nf.ProductionElement(pos, factor, nf
				.SemanticAction(pos, b));
		return production;
	}
}
